using System.IO;

namespace Dida
{
	/// <summary>
	/// An axis aligned box.
	/// </summary>
	public struct Box
	{
		/// <summary>
		/// The min corner of this box.
		/// </summary>
		public Vector3D min;
		/// <summary>
		/// The max corner of this box.
		/// </summary>
		public Vector3D max;

		/// <summary>
		/// Creates a box from the given parameters.
		/// </summary>
		/// <param name="min">The min corner of the box</param>
		/// <param name="max">The max corner of the box</param>
		public Box(Vector3D min, Vector3D max)
		{
			this.min = min;
			this.max = max;
		}

		/// <summary>
		/// Creates a box from the given parameters
		/// </summary>
		/// <param name="minx">The x coordinate of the min corner of the box</param>
		/// <param name="miny">The y coordinate of the min corner of the box</param>
		/// <param name="minz">The z coordinate of the min corner of the box</param>
		/// <param name="maxx">The x coordinate of the max corner of the box</param>
		/// <param name="maxy">The y coordinate of the max corner of the box</param>
		/// <param name="maxz">The z coordinate of the max corner of the box</param>
		public Box(float minx, float miny, float minz, float maxx, float maxy, float maxz)
		{
			min = new Vector3D(minx, miny, minz);
			max = new Vector3D(maxx, maxy, maxz);
		}

		public static Box FromCenterAndSize(Vector3D center, Vector3D size)
		{
			Box ret = new Box();
			ret.min = center - size * .5f;
			ret.max = center + size * .5f;
			return ret;
		}

		/// <summary>
		/// Sets this box to the box that contains both the current box, and
		/// the box in b.
		/// </summary>
		/// <param name="b">The box to union this box with</param>
		public void Union(Box b)
		{
			if (b.min.x < min.x)
				min.x = b.min.x;
			if (b.min.y < min.y)
				min.y = b.min.y;
			if (b.min.z < min.z)
				min.z = b.min.z;

			if (b.max.x > max.x)
				max.x = b.max.x;
			if (b.max.y > max.y)
				max.y = b.max.y;
			if (b.max.z > max.z)
				max.z = b.max.z;
		}

		/// <summary>
		/// Expands this box so that it contains the given point.
		/// </summary>
		/// <param name="pt">The point to expand to.</param>
		public void Expand(Vector3D pt)
		{
			if (pt.x < min.x)
				min.x = pt.x;
			if (pt.x > max.x)
				max.x = pt.x;
			if (pt.y < min.y)
				min.y = pt.y;
			if (pt.y > max.y)
				max.y = pt.y;
			if (pt.z < min.z)
				min.z = pt.z;
			if (pt.z > max.z)
				max.z = pt.z;
		}

		/// <summary>
		/// Gets the center of this box.
		/// </summary>
		public Vector3D Center
		{
			get
			{
				return (min + max) * .5f;
			}
		}

		/// <summary>
		/// Gets the extends vector of this box. The extends vector is the distance
		/// from the center of the box, to the max corner of the box.
		/// </summary>
		public Vector3D Extends
		{
			get
			{
				return (max - min) * .5f;
			}
		}

		/// <summary>
		/// <para>
		/// Gets a box, of which every point will be 
		/// higher than it's max corner, and lower than it's min corner.
		/// </para>
		/// <para>This box is handy when you are calculating the bounding box of
		/// multiple objects. You then start with this NullBox, and
		/// <see cref = "Union">Union</see> it with the bounding box of
		/// each object.</para>
		/// </summary>
		public static Box NullBox
		{
			get
			{
				return new Box(
					new Vector3D(float.MaxValue, float.MaxValue, float.MaxValue),
					new Vector3D(-float.MaxValue, -float.MaxValue, -float.MaxValue));
			}
		}

		/// <summary>
		/// <para>
		/// Returns the position of the corner indexed by the i parameter.
		/// </para>
		/// <para>
		/// The corner corresponding to an index can be found the following way:
		/// <list>
		/// <item>If the first bit is set, the x component will be set to 
		/// the x component of the max corner, otherwise it will be 
		/// set to the x component of the min corner.</item>
		/// <item>If the second bit is set, the y component will be set to 
		/// the y component of the max corner, otherwise it will be 
		/// set to the y component of the min corner.</item>
		/// <item>If the third bit is set, the z component will be set to 
		/// the z component of the max corner, otherwise it will be 
		/// set to the z component of the min corner.</item>
		/// </list>
		/// So the corner with index 5 (101 binary) would be 
		/// <c>Vector3D(max.x,min.y,max.z);</c>
		/// </para>
		/// </summary>
		/// <param name="i">The index of the corner of which to return the position</param>
		/// <returns>The position of the given corner</returns>
		public Vector3D GetCorner(int i)
		{
			Vector3D ret = new Vector3D();
			ret.x = (i & 1) == 0 ? min.x : max.x;
			ret.y = (i & 2) == 0 ? min.y : max.y;
			ret.z = (i & 4) == 0 ? min.z : max.z;
			return ret;
		}

		/// <summary>
		/// Creates a polygon representation of this box. These polygons
		/// are facing outward.
		/// </summary>
		/// <returns>An array of <see cref = "Polygon">Polygons</see> 
		/// that represent this box.</returns>
		public Polygon[] Polygonize()
		{
			Polygon[] res = new Polygon[6];

			// the positive x face
			res[0] = new Polygon(GetCorner(3),GetCorner(7),GetCorner(5),GetCorner(1));

			// the negative x face			
			res[1] = new Polygon(GetCorner(0),GetCorner(4),GetCorner(6),GetCorner(2));

			// the positive y face
			res[2] = new Polygon(GetCorner(6),GetCorner(7),GetCorner(3),GetCorner(2));

			// the negative y face
			res[3] = new Polygon(GetCorner(0),GetCorner(1),GetCorner(5),GetCorner(4));
			
			// the positive z face
			res[4] = new Polygon(GetCorner(7), GetCorner(6), GetCorner(4), GetCorner(5));
			
			// the negative z face
			res[5] = new Polygon(GetCorner(2), GetCorner(3), GetCorner(1), GetCorner(0));
			
			return res;
		}

		/// <summary>
		/// Returns whether this box and the box in b intersect.
		/// </summary>
		/// <param name="b">The second box of the intersection test.</param>
		/// <returns>True when they intersect. False otherwise.</returns>
		public bool Intersects(Box b)
		{
			return IntersectionTests.BoxBox(this, b);
		}

		/// <summary>
		/// Returns whether the given plane intersects this box.
		/// </summary>
		/// <param name="plane">The plane that's tested against this box.</param>
		/// <returns>true when the box and the plane intersect, false toherwise.</returns>
		public bool Intersects(Plane plane)
		{
			return IntersectionTests.PlaneBox(plane, this);
		}

		/// <summary>
		/// Returns whether the given sphere intersects this box.
		/// </summary>
		/// <param name="sphere">The sphere that's tested against this box.</param>
		/// <returns>true when the box and the sphere intersect, false toherwise.</returns>
		public bool Intersects(Sphere sphere)
		{
			return IntersectionTests.SphereBox(sphere, this);
		}

		/// <summary>
		/// Gets the bounding sphere of this box. The bounding sphere is
		/// the smallest sphere that contains the whole box.
		/// </summary>
		public Sphere BoundingSphere
		{
			get
			{
				Sphere ret = new Sphere();
				ret.center = (min + max) * .5f;
				ret.radius = (max - ret.center).Len * .5f;
				return ret;
			}
		}

		public void WriteBinary(BinaryWriter s)
		{
			min.WriteBinary(s);
			max.WriteBinary(s);
		}

#if XNA
		public static implicit operator Microsoft.Xna.Framework.BoundingBox(Box src)
		{
			return new Microsoft.Xna.Framework.BoundingBox(src.min, src.max);
		}

		public static implicit operator Box(Microsoft.Xna.Framework.BoundingBox src)
		{
			return new Box(src.Min, src.Max);
		}
#endif
	}
}
